package com.geneqiao.jdbc;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.log4j.Logger;

import com.geneqiao.jdbc.build.ISqlSession;
import com.geneqiao.jdbc.jpa.TableStruct;
import com.geneqiao.jdbc.pool.JDBCDataSource;
import com.geneqiao.jdbc.pool.JDBCMap;
import com.geneqiao.jdbc.util.DataUtils;
import com.geneqiao.jdbc.util.ObjectUtils;
import com.geneqiao.jdbc.util.SQLType;

public class DefaultSessionImpl extends AbstractSession implements ISqlSession
{

	private static final Object obj1 = new Object();

	// 日志
	private static final Logger logger = Logger.getLogger(DefaultSessionImpl.class);

	private static ISqlSession sessionProxy;

	private DefaultSessionImpl(JDBCDataSource dataSource)
	{
		this.jdbcDataSource = dataSource;
	}

	public static ISqlSession getInstence(JDBCDataSource dataSource)
	{
		ISqlSession session = new DefaultSessionImpl(dataSource);
		InvocationHandler invocationHandler = new DefaultInterceptor(session);
		sessionProxy = (ISqlSession)Proxy.newProxyInstance(session.getClass().getClassLoader(),  
				session.getClass().getInterfaces(), invocationHandler); 
		return sessionProxy;
	}

	@Override
	protected <T> List<T> buildQuery(Class<T> t, String sql, Connection conn, Object... objects) throws SQLException
	{
		long start = getNow();
		List<T> vos = new ArrayList<>();
		ResultSet rs = ObjectUtils.buildResultSet(conn, sql, objects);
		Map<Integer, Field> map;
		long start2 = getNow();
		String cacheKey = t.getName() + "_" + sql;
		// 需要同步执行，不然并发时会build多次
		synchronized (obj1)
		{
			if (JDBCMap.containsKey(cacheKey))
			{
				map = JDBCMap.get(cacheKey);
				if (logger.isDebugEnabled())
					logger.debug("Load map from cache => " + (getNow() - start2) + "ns");
			}
			else
			{
				map = DataUtils.buildColumnMap(t, rs.getMetaData());
				JDBCMap.put(cacheKey, map);
				if (logger.isDebugEnabled())
					logger.debug("Load map from metadata => " + (getNow() - start2) + "ns");
			}
		}
		if (map.size() > 0)
		{
			while (rs.next())
			{
				vos.add(DataUtils.copyValue(map, rs, t));
			}
		}
		rs.getStatement().close();
		rs.close();
		if (logger.isDebugEnabled())
		{
			logger.debug("SQL => " + sql);
			logger.debug("Result rows => " + vos.size() + "; columns => " + map.size() + "; time => "
					+ (getNow() - start) + "ns");
		}
		return vos;
	}

	@SuppressWarnings("unchecked")
	@Override
	protected <T> List<T> buildQueryList(String sql, Connection conn, Object... objects) throws SQLException
	{
		long start = getNow();
		ResultSet rs = ObjectUtils.buildResultSet(conn, sql, objects);
		List<T> dataList = new ArrayList<>();
		while (rs.next())
		{
			dataList.add((T) rs.getObject(1));
		}
		rs.getStatement().close();
		rs.close();
		if (logger.isDebugEnabled())
		{
			logger.debug("SQL => " + sql);
			logger.debug("Times => " + (getNow() - start) + "ns");
		}
		return dataList;
	}

	@Override
	protected Map<String, Object> buildQueryMap(String sql, Connection conn, Object... objects) throws SQLException
	{
		long start = getNow();
		ResultSet rs = ObjectUtils.buildResultSet(conn, sql, objects);
		if (rs.getMetaData().getColumnCount() != 2)
			throw new SQLException("字典查询只能包含两列字段");
		Map<String, Object> map = new HashMap<>();
		while (rs.next())
		{
			map.put(rs.getString(1), rs.getObject(2));
		}
		rs.getStatement().close();
		rs.close();
		if (logger.isDebugEnabled())
		{
			logger.debug("SQL => " + sql);
			logger.debug("Map<K,V> => " + map.size() + "; Times => " + (getNow() - start) + "ns");
		}
		return map;
	}

	@Override
	protected Object buildGetSingle(String sql, Connection conn, Object... objects) throws SQLException
	{
		long start = getNow();
		Object obj = null;
		ResultSet rs = ObjectUtils.buildResultSet(conn, sql, objects);
		if (rs.next())
			obj = rs.getObject(1);
		rs.getStatement().close();
		rs.close();
		if (logger.isDebugEnabled())
		{
			logger.debug("SQL => " + sql);
			logger.debug("Result => " + obj + "; Times => " + (getNow() - start) + "ns");
		}
		return obj;
	}

	@Override
	protected boolean buildExecute(String sql, Connection conn, Object... objects) throws SQLException
	{
		long start = getNow();
		Integer num = ObjectUtils.executeUpdate(conn, sql, objects);
		if (logger.isDebugEnabled())
		{
			logger.debug("SQL => " + sql);
			logger.debug("Rows => " + num + ", Times => " + (getNow() - start) + "ns");
		}
		return true;
	}

	@Override
	protected boolean buildSave(Connection conn, TableStruct struct, Object obj) throws SQLException
	{
		long start = getNow();
		// 重新赋值Columns
		struct.setColumns(obj);
		DataUtils.buildSQLMap(struct, SQLType.INSERT);
		Object result = null;
		PreparedStatement ps = ObjectUtils.buildStatement(conn, struct.getBuildMap(), struct.hasGenerageKey());
		if (ps.executeUpdate() > 0 && struct.hasGenerageKey())
		{
			ResultSet rs = ps.getGeneratedKeys();
			if (rs.next())
				result = rs.getInt(1);
			rs.close();
			if (result != null)
				DataUtils.setPrimaryKey(obj, struct.getGenerateKey(), result);
		}
		ps.close();
		if (logger.isDebugEnabled())
			logger.debug("Saved, ID => " + result + ", Times => " + (getNow() - start) + "ns");
		return true;
	}

	@Override
	protected boolean buildSaveOrUpdate(Connection conn, Object obj) throws SQLException
	{
		TableStruct struct = getEntity(obj.getClass());
		// 重新赋值Columns
		struct.setColumns(obj);
		if (struct.hasPrimaryKey() && struct.getColumns().get(struct.getPrimaryKey()) != null)
		{
			logger.debug("发现主键有值，定义为update");
			return buildUpdate(conn, obj, true);
		}
		else
		{
			logger.debug("没有发现主键的value，定义为insert");
			return buildSave(conn, struct, obj);
		}
	}

	@Override
	protected boolean saveList(List<?> objs)
	{
		long start = getNow();
		Connection conn = null;
		try
		{
			conn = jdbcDataSource.getConnection();
			int num = 0;
			for (Object obj : objs)
			{
				buildSave(conn, getEntity(obj.getClass()), obj);
				num++;
			}
			if (logger.isDebugEnabled())
			{
				logger.debug("Finish, total => " + objs.size() + ", saved => " + num + ", Times => "
						+ (getNow() - start) + "ns");
			}
			return true;
		}
		catch (SQLException e)
		{
			logger.error("Save object faild", e);
			return false;
		}
		finally
		{
			jdbcDataSource.releaseConnection(conn);
		}
	}

	@Override
	protected boolean buildUpdate(Connection conn, Object obj, boolean notNull) throws SQLException
	{
		long start = getNow();
		TableStruct struct = getEntity(obj.getClass());
		if (!struct.hasPrimaryKey())
			throw new SQLException("获取对象" + obj.getClass().getName() + "的主键失败");
		struct.setColumns(obj);
		DataUtils.buildSQLMap(struct, (notNull ? SQLType.UPDATENOTNULL : SQLType.UPDATE));
		PreparedStatement ps = ObjectUtils.buildStatement(conn, struct.getBuildMap());
		Integer num = ps.executeUpdate();
		ps.close();
		if (logger.isDebugEnabled())
			logger.debug("Updated, Rows => " + num + ", Times => " + (getNow() - start) + "ns");
		return true;
	}

	@Override
	protected boolean buildDelete(Connection conn, Object obj) throws SQLException
	{
		long start = getNow();
		TableStruct struct = getEntity(obj.getClass());
		if (!struct.hasPrimaryKey())
			throw new SQLException("获取对象" + obj.getClass().getName() + "的主键失败");
		struct.setColumns(obj);
		DataUtils.buildSQLMap(struct, SQLType.DELETE);
		PreparedStatement ps = ObjectUtils.buildStatement(conn, struct.getBuildMap());
		Integer num = ps.executeUpdate();
		ps.close();
		if (logger.isDebugEnabled())
			logger.debug("Deleted, Rows => " + num + ", Times => " + (getNow() - start) + "ns");
		return true;
	}
}
